﻿using System;
using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using CommonFramework.Log;
using NSubstitute;
using NSubstitute.ReceivedExtensions;

namespace CommonFrameworkTest
{
    [TestFixture]
    public class TestLog
    {
        public List<LogMessage> RecvLogMessages = new List<LogMessage>();

        [OneTimeSetUp]
        public void BeforeAnyTests()
        {
            LogUtil.ListenLogMessage((msg, backtrace) =>
            {
                if (backtrace)
                    return;
                RecvLogMessages.Add(msg);
            });
        }

        [SetUp]
        public void BeforePerTest()
        {
            RecvLogMessages.Clear();
        }

        [Test]
        public void TestLogMessage_Create()
        {
            Assert.That(RecvLogMessages.Count,Is.EqualTo(0));
            LogUtil.Log("Hello...");
            Assert.That(RecvLogMessages.Count,Is.EqualTo(1));

            LogMessage msg = RecvLogMessages.First();
            Assert.That(msg.Severity, Is.EqualTo(LogSeverity.Info));
            Assert.That(msg.GetContent().Contains("Hello..."));
        }

        [Test]
        public void TestLogError_WithException_ContainsStackTraceOfException()
        {
            Exception catchedException = null;
            try
            {
                throw new Exception("fake exception");
            }
            catch (Exception e)
            {
                LogUtil.LogError("error happen", e);
                catchedException = e;
            }

            Assert.That(RecvLogMessages.Count == 1);
            LogMessage msg = RecvLogMessages.First();

            Assert.That(msg.Context is Exception);
            Assert.That(msg.GetContent(), Does.Contain("fake exception"));
            Assert.That(msg.GetContent(), Does.Contain(catchedException.StackTrace));
        }

        [Test]
        public void TestLogHistoryService_Actived_AddCacheMessage()
        {
            int count = LogHistoryService.GetCacheMessageCount();
            LogHistoryService.IsActived = true;

            LogUtil.Log("test");
            int newCount = LogHistoryService.GetCacheMessageCount();

            Assert.That(newCount, Is.EqualTo(count + 1));
        }

        [Test]
        public void TestLogHistoryService_Disactived_NotAddCacheMessage()
        {
            int count = LogHistoryService.GetCacheMessageCount();
            LogHistoryService.IsActived = false;

            LogUtil.Log("test");
            int newCount = LogHistoryService.GetCacheMessageCount();

            Assert.That(newCount, Is.EqualTo(count));
        }

        [Test]
        public void TestLogHistoryService_Timeout_RemoveCachedMessage()
        {
            LogHistoryService.IsActived = true;
            LogUtil.Log("test");
            int count = LogHistoryService.GetCacheMessageCount();
            Assert.That(count > 0);

            for (int i = 0; i < LogHistoryService.MAX_LOGMESSAGE_CACHE_SECONDS; ++i)
                LogHistoryService.OnSecondUpdate();

            int newCount = LogHistoryService.GetCacheMessageCount();
            Assert.That(newCount, Is.LessThan(count));
        }

        [Test]
        public void TestLogHistoryService_VeryMuchLog_LimitedWithMaxCache()
        {
            LogHistoryService.IsActived = true;
            for (int i = 0; i < LogHistoryService.MAX_LOGMESSAGE_CACHE_COUNT + 1; ++i)
                LogUtil.Log("test " + i);

            int count = LogHistoryService.GetCacheMessageCount();
            Assert.That(count, Is.LessThanOrEqualTo(LogHistoryService.MAX_LOGMESSAGE_CACHE_COUNT));
        }

        [Test]
        public void TestGetLogHash_SameLogs_SameHashCode()
        {
            Exception toThrow = new Exception("fake exception");
            for (int i = 0; i < 2; ++i)
            {
                try
                {
                    throw toThrow;
                }
                catch (Exception e)
                {
                    LogUtil.LogError("fake error",e);
                }
            }

            Assert.That(RecvLogMessages.Count, Is.EqualTo(2));
            Assert.That(RecvLogMessages[0].GetHashCode(), Is.EqualTo(RecvLogMessages[1].GetHashCode()));
        }

        [Test]
        public void TestGetLogHash_DifferentSeverity_NotSameHashCode()
        {
            for (int i = 0; i < 2; ++i)
                LogUtil.LogError("fake error");

            Assert.That(RecvLogMessages.Count, Is.EqualTo(2));
            var log1 = RecvLogMessages[0];
            var log2 = RecvLogMessages[1];

            log2.GetModifier().Severity = LogSeverity.Warning;
            Assert.That(log1.GetHashCode(), Is.Not.EqualTo(log2.GetHashCode()));
        }

        [Test]
        public void TestGetLogHash_DifferentContent_NotSameHashCode()
        {
            for (int i = 0; i < 2; ++i)
                LogUtil.LogError("fake error" + i);

            Assert.That(RecvLogMessages.Count, Is.EqualTo(2));
            var log1 = RecvLogMessages[0];
            var log2 = RecvLogMessages[1];
            Assert.That(log1.GetHashCode(), Is.Not.EqualTo(log2.GetHashCode()));
        }

        [Test]
        public void TestGetLogHash_DifferentContext_NotSameHashCode()
        {
            for (int i = 0; i < 2; ++i)
                LogUtil.LogError("fake error" + i, new Exception());

            Assert.That(RecvLogMessages.Count, Is.EqualTo(2));
            var log1 = RecvLogMessages[0];
            var log2 = RecvLogMessages[1];
            Assert.That(log1.GetHashCode(), Is.Not.EqualTo(log2.GetHashCode()));
        }

        [Test]
        public void TestGetLogHash_ModifyLog_HashCodeChnanged()
        {
            LogUtil.LogError("fake error", new Exception());

            Assert.That(RecvLogMessages.Count, Is.EqualTo(1));
            var log = RecvLogMessages[0];
            int hash1 = log.GetHashCode();
            log.GetModifier().Severity = LogSeverity.Warning;
            int hash2 = log.GetHashCode();
            log.GetModifier().Severity = LogSeverity.Error;
            int hash3 = log.GetHashCode();

            Assert.That(hash1, Is.Not.EqualTo(hash2));
            Assert.That(hash1, Is.EqualTo(hash3));
        }

        [Test]
        public void TestOnLogMessage_Default_CallWithBackTrace()
        {
            List<string> order = new List<string>();
            LogUtil.ListenLogMessage((msg, backtrace) => { order.Add("method1" + backtrace); });
            LogUtil.ListenLogMessage((msg, backtrace) => { order.Add("method2" + backtrace); });

            LogUtil.LogError("fake error");

            Assert.That(order.Count, Is.EqualTo(4));
            Assert.That(order[0], Is.EqualTo("method1False"));
            Assert.That(order[1], Is.EqualTo("method2False"));
            Assert.That(order[2], Is.EqualTo("method2True"));
            Assert.That(order[3], Is.EqualTo("method1True"));
        }

        public class FakeLogMessageHandler : LogMessageHandler
        {
            public bool fakeNeedBackTrace;
            public List<bool> OnLogMessageCalled = new List<bool>();

            public override bool NeedBackTrace() => fakeNeedBackTrace;
            protected override void OnLogMessage(LogMessage message, bool isBackTrace)
            {
                OnLogMessageCalled.Add(isBackTrace);
            }
        }

        [Test]
        public void TestOnLogMessage_HandlerNotNeedBackTrace_CallOnce()
        {
            var handler = new FakeLogMessageHandler();
            handler.fakeNeedBackTrace = false;

            LogUtil.LogError("fake error");

            Assert.That(handler.OnLogMessageCalled.Count,Is.EqualTo(1));
            Assert.That(handler.OnLogMessageCalled[0],Is.False);
        }

        [Test]
        public void TestOnLogMessage_HandlerNeedBackTrace_CallTwice()
        {
            var handler = new FakeLogMessageHandler();
            handler.fakeNeedBackTrace = true;

            LogUtil.LogError("fake error");

            Assert.That(handler.OnLogMessageCalled.Count, Is.EqualTo(2));
            Assert.That(handler.OnLogMessageCalled[0], Is.False);
            Assert.That(handler.OnLogMessageCalled[1], Is.True);
        }
    }
}
